
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/rtc.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/spi/spi.h>
#include <linux/gpio.h>

#define DBGMSG

#ifdef DBGMSG
#define DBG( args... ) dev_info( args )
#else
#define DBG( args... )
#endif

#define SABRESD_MCPSPI_CS			IMX_GPIO_NR( 4, 24 )

#define MCP795_BUFADDR_INST		0
#define MCP795_BUFADDR_ADDR		1
#define MCP795_BUFADDR_DATA		2

#define MCP795_INST_EEREAD		0x03
#define MCP795_INST_EEWRITE		0x02
#define MCP795_INST_EEWRDI		0x04
#define MCP795_INST_EEWREN		0x06
#define MCP795_INST_SRREAD		0x05
#define MCP795_INST_SRWRITE		0x01
#define MCP795_INST_READ			0x13
#define MCP795_INST_WRITE			0x12
#define MCP795_INST_UNLOCK		0x14
#define MCP795_INST_IDWRITE		0x32
#define MCP795_INST_IDREAD		0x33
#define MCP795_INST_CLRWDT		0x44
#define MCP795_INST_CLRRAM		0x54

#define MCP795_REG_HSECONDS			0x00
#define MCP795_REG_SECOND				0x01
#define MCP795_REG_MINUTE				0x02
#define MCP795_REG_HOUR					0x03
#define MCP795_REG_DAY					0x04
#define MCP795_REG_DATE					0x05
#define MCP795_REG_MONTH				0x06
#define MCP795_REG_YEAR					0x07
#define MCP795_REG_CONTROL			0x08
#define MCP795_REG_CALIBRATION	0x09
#define MCP795_REG_WATCHDOG			0x0A
#define MCP795_REG_EVENT				0x0B

#define MCP795_BIT_ST						0x80
#define MCP795_BIT_CALSGN				0x80
#define MCP795_BIT_1224					0x40
#define MCP795_BIT_AMPM					0x20
#define MCP795_BIT_OSCON				0x20
#define MCP795_BIT_VBAT					0x10
#define MCP795_BIT_VBATEN				0x08
#define MCP795_BIT_LP						0x20
#define MCP795_BIT_OUT					0x80
#define MCP795_BIT_SQWE					0x40
#define MCP795_BIT_ALM1					0x20
#define MCP795_BIT_ALM2					0x10
#define MCP795_BIT_EXTOSC				0x08
#define MCP795_BIT_RS2					0x04
#define MCP795_BIT_RS1					0x02
#define MCP795_BIT_RS0					0x01

#define MCP795_ST_BIT		0x80
#define MCP795_24_BIT		0x40

struct mcp795_info {
	struct spi_device* spi;
	struct rtc_device* rtc;

	struct work_struct work;

	unsigned long flags;
	bool hr12;
};

static int mcp795_inst_rtcc( struct device* dev, u8 inst, u8 addr, u8* data, u8 count )
{
	int ret = 0;
	struct spi_device* spi = to_spi_device( dev );
	u8 command[ 2 + count ];

	command[ MCP795_BUFADDR_INST ] = inst;
	command[ MCP795_BUFADDR_ADDR ] = addr;

	gpio_set_value( SABRESD_MCPSPI_CS, 0 );
	ndelay( 10 );
	
	switch( inst ) {
		case MCP795_INST_READ:
			ret = spi_write_then_read( spi, command, 2, data, count );
			if( ret != 0 )
				dev_err( dev, "Failed reading [0x%X:0x%X:0x%x].\n", inst, addr, count );
			break;
			
		case MCP795_INST_WRITE:
			memcpy( &command[ MCP795_BUFADDR_DATA ], data, count );
			ret = spi_write( spi, command, 2 + count );
			if( ret != 0 )
				dev_err( dev, "Failed to write [0x%X:0x%X:0x%X].\n", inst, addr, count );
			break;
		
		default:
			dev_err( dev, "Unknow instruction for RTCC.\n" );
			break;
	}

	ndelay( 10 );
	gpio_set_value( SABRESD_MCPSPI_CS, 1 );
	
	return ret;
}

static int mcp795_inst_rtcc_getreg( struct device* dev, u8 addr )
{
	u8 raw;

	mcp795_inst_rtcc( dev, MCP795_INST_READ, addr, &raw, 1 );

	return raw;
}

static u8 mcp795_inst_rtcc_setreg( struct device* dev, u8 addr, u8 value )
{	
	u8 raw = value;
	
	mcp795_inst_rtcc( dev, MCP795_INST_WRITE, addr, &raw, 1 );
	
	return raw;
}

static int mcp795_rtc_read_time( struct device* dev, struct rtc_time* tim )
{
	int ret;
	u8 data[ 7 ];

	DBG( dev, "%s(%d)\n", __FUNCTION__, __LINE__ );

	ret = mcp795_inst_rtcc( dev, MCP795_INST_READ, 0x01, data, sizeof( data ) );
	if( ret != 0 )
		dev_err( dev, "%s(%d) Failed.\n", __FUNCTION__, __LINE__ );

	tim->tm_sec =  ( ( data[ 0 ] & 0x70 ) >> 4 ) * 10 + ( data[ 0 ] & 0x0F );
	tim->tm_min =  ( ( data[ 1 ] & 0x70 ) >> 4 ) * 10 + ( data[ 1 ] & 0x0F );
	tim->tm_hour = ( ( data[ 2 ] & 0x30 ) >> 4 ) * 10 + ( data[ 2 ] & 0x0F );
	tim->tm_mday = ( ( data[ 4 ] & 0x30 ) >> 4 ) * 10 + ( data[ 4 ] & 0x0F );
	tim->tm_mon =  ( ( data[ 5 ] & 0x10 ) >> 4 ) * 10 + ( data[ 5 ] & 0x0F );
	tim->tm_year = ( ( data[ 6 ] & 0xF0 ) >> 4 ) * 10 + ( data[ 6 ] & 0x0F ) + 100; /* Assume we are in 20xx */

	DBG( dev, "Get from mcp795: %04d-%02d-%02d %02d:%02d:%02d [%X %X %X %X %X %X %X]\n",
		tim->tm_year + 1900, tim->tm_mon, tim->tm_mday,
		tim->tm_hour, tim->tm_min, tim->tm_sec,
		data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] );

	return rtc_valid_tm( tim );
}

static int mcp795_rtc_set_time( struct device* dev, struct rtc_time* tim )
{
	u8 ret = 0;
	u8 data[ 7 ];
	
	DBG( dev, "%s(%d)\n", __FUNCTION__, __LINE__ );

	ret = mcp795_inst_rtcc( dev, MCP795_INST_READ, 0x01, data, sizeof( data ) );
	if( ret != 0 )
		dev_err( dev, "%s(%d) Failed.\n", __FUNCTION__, __LINE__ );

	tim->tm_year = tim->tm_year > 100 ? tim->tm_year - 100 : tim->tm_year;
		
	data[ 0 ] = ( ( tim->tm_sec / 10 ) << 4 ) | ( tim->tm_sec % 10 );
	data[ 1 ] = ( ( tim->tm_min / 10 ) << 4 ) | ( tim->tm_min % 10 );
	data[ 2 ] = ( ( tim->tm_hour / 10 ) << 4 ) | ( tim->tm_hour % 10 );
	data[ 3 ] = 0; // Weekday 1-7
	data[ 4 ] = ( ( tim->tm_mday / 10 ) << 4 ) | ( tim->tm_mday % 10 );
	data[ 5 ] = ( ( tim->tm_mon / 10 ) << 4 ) | ( tim->tm_mon % 10 );
	data[ 6 ] = ( ( tim->tm_year / 10 ) << 4 ) | ( tim->tm_year % 10 );

	data[ 0 ] |= MCP795_BIT_ST;
	data[ 3 ] |= MCP795_BIT_VBATEN;

	ret = mcp795_inst_rtcc( dev, MCP795_INST_WRITE, 0x01, data, sizeof( data ) );
	if( ret != 0 )
		dev_err( dev, "%s(%d) Failed.\n", __FUNCTION__, __LINE__ );

	DBG( dev, "Set mcp795: %04d-%02d-%02d %02d:%02d:%02d [%X %X %X %X %X %X %X]\n",
		tim->tm_year + 1900, tim->tm_mon, tim->tm_mday,
		tim->tm_hour, tim->tm_min, tim->tm_sec,
		data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] );

	return ret;
}

static struct rtc_class_ops mcp795_rtc_ops = {
	.read_time = mcp795_rtc_read_time,
	.set_time = mcp795_rtc_set_time,
};

static int __devinit mcp795_probe( struct spi_device *spi )
{
	u8 reg;
	struct rtc_time tim;
	int ret = 0;
	struct mcp795_info* mcp795 = 0;

	DBG( &spi->dev, "%s(%d)\n", __FUNCTION__, __LINE__ );

	mcp795 = kzalloc( sizeof *mcp795, GFP_KERNEL );
	if( mcp795 == 0 )
		return -ENOMEM;

	spi->mode = SPI_MODE_0;
	spi->bits_per_word = 8;
	ret = spi_setup( spi );
	if( ret != 0 ) {
		dev_err( &spi->dev, "Unable to setup SPI for MCP795.\n" );
		goto mcp795_probe_errout;
	}
	mcp795->spi = spi;
	spi_set_drvdata( spi, mcp795 );

	gpio_request( SABRESD_MCPSPI_CS, "MCPSPI_CS" );
	gpio_direction_output( SABRESD_MCPSPI_CS, 1 );
	msleep(1);
	gpio_set_value( SABRESD_MCPSPI_CS, 1 );
	msleep(1);

	reg = mcp795_inst_rtcc_getreg( &spi->dev, MCP795_REG_DAY );
	if( reg & MCP795_BIT_OSCON ) {
		dev_info( &spi->dev, "RTC is alive during power off!\n" );
	} else {
		dev_info( &spi->dev, "RTC initialize!\n" );

		reg |= MCP795_BIT_VBATEN;
		mcp795_inst_rtcc_setreg( &spi->dev, MCP795_REG_DAY, reg );

		mcp795_inst_rtcc_setreg( &spi->dev, MCP795_REG_CONTROL, MCP795_BIT_SQWE );
		mcp795_inst_rtcc_setreg( &spi->dev, MCP795_REG_SECOND, 0x00 );
		mcp795_inst_rtcc_setreg( &spi->dev, MCP795_REG_MINUTE, 0x00 );
		mcp795_inst_rtcc_setreg( &spi->dev, MCP795_REG_SECOND, MCP795_BIT_ST );
	}
	mcp795_rtc_read_time( &spi->dev, &tim );

	mcp795->rtc = rtc_device_register( "mcp795", &spi->dev, &mcp795_rtc_ops, THIS_MODULE );
	if( IS_ERR( mcp795->rtc ) ) {
		ret = PTR_ERR( mcp795->rtc );
		dev_err( &spi->dev, "Unable to register MCP795 into SPI.\n" );
		goto mcp795_probe_errout;
	}

	return ret;

mcp795_probe_errout:
	if( mcp795 != 0 )
		kfree( mcp795 );

	return ret;
}

static int __devexit mcp795_remove( struct spi_device* spi )
{
	struct mcp795_info* mcp795 = spi_get_drvdata( spi );

	DBG( &spi->dev, "%s(%d)\n", __FUNCTION__, __LINE__ );

	rtc_device_unregister( mcp795->rtc );

	spi_set_drvdata( spi, NULL );

	kfree( mcp795 );

	return 0;
}

static struct spi_driver mcp795_spi_driver = {
	.driver.name = "rtc-mcp795",
	.driver.owner = THIS_MODULE,
	.probe = mcp795_probe,
	.remove = __devexit_p( mcp795_remove ),
};

static int __init mcp795_rtc_init( void )
{
	return spi_register_driver( &mcp795_spi_driver );
}
module_init( mcp795_rtc_init );

static void __exit mcp795_rtc_exit( void )
{
	spi_unregister_driver( &mcp795_spi_driver );
}
module_exit( mcp795_rtc_exit );

MODULE_AUTHOR( "William Chen" );
MODULE_DESCRIPTION( "Microchip MCP795 Realtime Clock Driver (RTC)" );
MODULE_LICENSE( "GPL" );


